import logging
from config import FUNCTION_SEARCH_ID, BATCH_SIZE
from blls.base_bll import BaseBLL
from dtos.result import Result
from enumobjects.general_enum import YesOrNoEnum
from models.file_models.file_model import File
from models.standardized_models.application_model import Application
from utils.external_api_check import ExternalApiCheck
from utils.chroma_db import Chroma


class FileSearchBLL(BaseBLL):

    @classmethod
    def get_embedding(cls, model, text: str) -> list[float]:
        try:
            embedding = model.encode(text,show_progress_bar=False,normalize_embeddings=True,batch_size=BATCH_SIZE)
            if len(embedding) == 0:
                logging.error(f"生成空向量：{text[:50]}")
                return []
            return embedding.tolist()
        except Exception as e:
            logging.error(f"向量化失败: {str(e)},文本内容:{text}")
            return []


    def _get_file_name(self, file_path: str) -> str:
        """根据文件路径从 MySQL 查询文件名"""
        try:
            # 假设使用 SQLAlchemy 的 session
            session = self.db_session
            file = session.query(File).filter(File.file_path == file_path, File.is_delete == YesOrNoEnum.NO.value).first()
            if file:
                return file.file_name
            else:
                return "未知文件名"
        except Exception as e:
            logging.error(f"获取文件名失败: {str(e)}")
            return "获取失败"


    def get_application(self, app_id):
        application_obj = self.standardized_session.query(Application).filter(
            Application.is_delete == YesOrNoEnum.NO.value,
            Application.is_disable == YesOrNoEnum.NO.value,
            Application.app_id == app_id
        ).first()
        return application_obj

    def check_sign(self, api_secret, function_ids_str, **data):
        function_ids = function_ids_str.split(",")
        if FUNCTION_SEARCH_ID not in function_ids:
            return Result.error_result(message="该应用无法访问该服务", code=402)
        check_res = ExternalApiCheck(api_secret=api_secret).check(**data)
        return check_res

    def _validate_application(self, app_id):
        """验证应用是否存在"""
        return self.get_application(app_id)

    def _check_api_permission(self, application_obj, **data):
        """检查API权限"""
        check_res = self.check_sign(
            application_obj.server_secret,
            application_obj.function_ids_str,
            **data
        )
        return check_res.is_success

    def _generate_query_embedding(self, query_text):
        """生成查询向量"""
        return self.get_embedding(self.request.model, query_text)

    def _build_where_filter(self, app_id, scene):
        """构建过滤条件"""
        conditions = [{"app_id": {"$eq": app_id}}]
        if scene:
            conditions.append({"scene": {"$eq": scene}})
        return {"$and": conditions} if len(conditions) > 1 else conditions[0]

    def _count_valid_docs(self, collection, where_filter):
        """统计符合条件的文档数"""
        try:
            filtered_docs = collection.get(where=where_filter, include=[])
            return len(filtered_docs['ids'])
        except Exception as e:
            logging.error(f"获取文档数量失败: {str(e)}")
            return 0

    def _adjust_n_results(self, valid_doc_count, top_k):
        """动态调整返回结果数"""
        max_request = top_k * 5
        adjusted_n = min(max_request, valid_doc_count)
        return max(adjusted_n, 1) if valid_doc_count > 0 else 0  # 至少返回1条

    def _execute_search(self, collection, query_emb, where_filter, n_results):
        """执行相似度搜索"""
        return collection.query(
            query_embeddings=[query_emb],
            where=where_filter,
            n_results=n_results,
            include=["metadatas", "distances"]
        )

    def _aggregate_results(self, results, top_k):
        """聚合结果并排序"""
        file_scores = {}
        for idx in range(len(results["ids"][0])):
            metadata = results["metadatas"][0][idx]
            distance = results["distances"][0][idx]
            file_id = metadata["file_id"]

            if file_id not in file_scores or distance < file_scores[file_id]["min_distance"]:
                file_scores[file_id] = {
                    "file_name": self._get_file_name(file_id),
                    "file_path": file_id,
                    "min_distance": distance,
                    "best_segment_id": results["ids"][0][idx]
                }

        sorted_files = sorted(file_scores.values(), key=lambda x: x["min_distance"])[:top_k]
        return [{
            "file_name": f["file_name"],
            "file_path": f["file_path"],
            "distance": f["min_distance"],
            "best_segment_id": f["best_segment_id"]
        } for f in sorted_files]
    def search(self, **res_data):
        collection = Chroma().chromadb()
        try:
            # 1. 参数校验与基础数据准备
            app_id = res_data["app_id"]
            query_text = res_data.get("query", "")
            scene = res_data.get("scene", "")
            top_k = res_data['top_k']

            # 2. 权限校验与应用信息获取
            application_obj = self._validate_application(app_id)
            if not application_obj:
                return Result.error_result(message="未找到对应的应用")

            if not self._check_api_permission(application_obj, **res_data):
                return Result.error_result(message="无权限访问该服务", code=402)

            # 3. 生成查询向量
            query_emb = self._generate_query_embedding(query_text)
            if not query_emb:
                return Result.error_result(message="无法生成查询向量", code=400)

            # 4. 构造过滤条件
            where_filter = self._build_where_filter(app_id, scene)

            # 5. 执行查询前的预处理
            valid_doc_count = self._count_valid_docs(collection, where_filter)

            # 6. 动态调整查询参数并执行查询
            adjusted_n_results = self._adjust_n_results(valid_doc_count, top_k)
            results = self._execute_search(collection, query_emb, where_filter, adjusted_n_results)

            # 7. 结果处理与聚合
            final_results = self._aggregate_results(results, top_k)

            return Result.success_result(data={"results": final_results})
        except Exception as e:
            logging.error(f"搜索失败: {str(e)}")
            return Result.error_result(message="搜索失败", code=500)